package com.chen.authority.service.impl;

import cn.dev33.satoken.secure.SaSecureUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.chen.authority.common.constant.SysConstant;
import com.chen.authority.common.model.PageQo;
import com.chen.authority.common.response.Response;
import com.chen.authority.common.util.ExcelUtil;
import com.chen.authority.mapper.DepartmentMapper;
import com.chen.authority.mapper.PostMapper;
import com.chen.authority.mapper.UserMapper;
import com.chen.authority.mapper.UserRoleMapper;
import com.chen.authority.model.dto.UserDto;
import com.chen.authority.model.qo.UserQo;
import com.chen.authority.model.po.*;
import com.chen.authority.model.vo.UserVo;
import com.chen.authority.service.RoleService;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import com.chen.authority.service.UserService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 用户Service业务层处理
 *
 * @author chengy
 * @date 2022-08-20
 */
@Service
@RequiredArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, UserPo> implements UserService {

    private final RoleService roleService;
    private final UserRoleMapper userRoleMapper;
    private final DepartmentMapper departmentMapper;
    private final PostMapper postMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Response addOrUpdateUser(Integer id, UserDto dto) {
        boolean notNull = ObjectUtil.isNotNull(id);

        boolean exists = baseMapper.exists(Wrappers
                .<UserPo>lambdaQuery()
                .and(sql -> sql
                        .eq(UserPo::getUsername, dto.getUsername())
                        .or()
                        .eq(UserPo::getEmail, dto.getEmail())
                        .or()
                        .eq(UserPo::getPhoneNumber, dto.getPhoneNumber()))
                .ne(notNull, UserPo::getId, id));
        if (exists) return Response.fail("用户名或邮箱或手机号重复，请检查");

        DepartmentPo department = departmentMapper.selectById(dto.getDepartmentId());
        if (ObjectUtil.isNull(department) && department.getType() != SysConstant.DEPARTMENT) {
            return Response.fail("部门选择有误，请检查");
        }

        UserPo user = dto.toPo(UserPo.class);

        if (!notNull) {
            user.setPassword(SaSecureUtil.md5(user.getPassword()));
            baseMapper.insert(user);
        } else {
            user.setId(id);
            baseMapper.updateById(user);
        }

        List<Integer> roleIds = dto.getRoleIds();
        if (CollUtil.isNotEmpty(roleIds)) {
            userRoleMapper.delete(Wrappers
                    .<UserRolePo>lambdaQuery()
                    .eq(UserRolePo::getUserId, user.getId()));
            List<UserRolePo> userRoles = new ArrayList<>();
            roleIds.forEach(roleId -> userRoles.add(new UserRolePo(user.getId(), roleId)));
            userRoleMapper.insertBatch(userRoles);
        }

        return Response.success();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Response deleteUserByIds(Integer[] ids) {
        baseMapper.deleteBatchIds(Arrays.asList(ids));
        userRoleMapper.delete(Wrappers.<UserRolePo>lambdaQuery().in(UserRolePo::getUserId, ids));
        return Response.success();
    }

    @Override
    public UserVo getUserInfoById(Integer id) {
        UserVo user = baseMapper.selectVoById(id);
        user.setRoles(roleService.findRolesByUserId(id));
        user.setPassword(null);
        return user;
    }

    @Override
    public Page<UserVo> pageQueryUserList(PageQo pageQo, UserQo userQo) {
        return baseMapper.selectUserListByPageAndCondition(pageQo.build(), userQo);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Response importUserList(MultipartFile file) {
        List<UserVo> users = ExcelUtil.importExcel(file, UserVo.class);

        if (CollUtil.isNotEmpty(users)) {
            // 校验上传文件中，是否存在相同用户名或邮箱或手机号
            Set<String> set = new HashSet<>();
            if (checkUsernameOrEmailOrPhoneNumberUnique(set, null, users)) {
                return Response.fail("上传文件中含有相同用户名或邮箱或手机号的用户");
            }

            // 获取全部用户名和邮箱和手机号，批量比对是否重复
            List<UserPo> userList = baseMapper.selectList(Wrappers.emptyWrapper());
            Stream<String> usernameStream = userList.stream()
                    .map(UserPo::getUsername);
            Stream<String> emailStream = userList.stream()
                    .map(UserPo::getEmail);
            Stream<String> phoneNumberStream = userList.stream()
                    .map(UserPo::getPhoneNumber);
            List<String> usernameAndEmailAndPhoneNumberList = Stream
                    .concat(Stream.concat(usernameStream, emailStream), phoneNumberStream)
                    .collect(Collectors.toList());
            if (checkUsernameOrEmailOrPhoneNumberUnique(set, usernameAndEmailAndPhoneNumberList, users)) {
                return Response.fail("含有与历史用户【用户名或邮箱或手机号重复】的记录");
            }

            // 获取所有非空部门用户，并进行校验，设置部门id
            List<UserVo> hasDepartmentUsers = users.stream()
                    .filter(user -> StringUtils.isNotBlank(user.getDepartmentName()))
                    .collect(Collectors.toList());
            if (CollUtil.isNotEmpty(hasDepartmentUsers)) {
                Map<String, DepartmentPo> departmentMap = departmentMapper.selectList(Wrappers.emptyWrapper())
                        .stream()
                        .collect(Collectors.toMap(DepartmentPo::getName, Function.identity()));
                for (UserVo user : hasDepartmentUsers) {
                    String[] departmentNames = user.getDepartmentName().split(StrPool.COMMA);
                    if (ArrayUtil.isEmpty(departmentNames)) {
                        return Response.fail("存在用户部门路径分隔有误");
                    }
                    DepartmentPo department = departmentMap.get(departmentNames[departmentNames.length - 1]);
                    if (ObjectUtil.isNull(department) || department.getType() != SysConstant.DEPARTMENT) {
                        return Response.fail("存在用户部门层级分配有误");
                    }
                    user.setDepartmentId(department.getId());
                }
            }

            // 获取所有非空岗位用户，并进行校验，设置岗位id
            List<UserVo> hasPostUsers = users.stream()
                    .filter(user -> StringUtils.isNotBlank(user.getPostName()))
                    .collect(Collectors.toList());
            if (CollUtil.isNotEmpty(hasPostUsers)) {
                Map<String, Integer> postMap = postMapper.selectList(Wrappers.emptyWrapper())
                        .stream()
                        .collect(Collectors.toMap(PostPo::getName, PostPo::getId));
                for (UserVo user : hasPostUsers) {
                    Integer postId = postMap.get(user.getPostName());
                    if (ObjectUtil.isNull(postId)) {
                        return Response.fail("存在用户岗位名称有误");
                    }
                    user.setPostId(postId);
                }
            }

            // 设置默认密码
            users.forEach(user -> user.setPassword(SaSecureUtil.md5(SysConstant.DEFAULT_PASSWORD)));

            baseMapper.insertUsers(users);

            // 获取所有非空角色用户，并进行校验，批量插入
            List<UserVo> hasRoleUsers = users.stream()
                    .filter(user -> StringUtils.isNotBlank(user.getRoleName()))
                    .collect(Collectors.toList());
            if (CollUtil.isNotEmpty(hasRoleUsers)) {
                Map<String, Integer> roleMap = roleService.list(Wrappers.emptyWrapper())
                        .stream()
                        .collect(Collectors.toMap(RolePo::getName, RolePo::getId));
                List<UserRolePo> userRoles = new ArrayList<>();
                for (UserVo user : hasRoleUsers) {
                    String[] roleNames = user.getRoleName().split(StrPool.COMMA);
                    for (String roleName : roleNames) {
                        Integer roleId = roleMap.get(roleName);
                        if (ObjectUtil.isNull(roleId)) {
                            throw new RuntimeException("存在用户角色名称有误");
                        }
                        userRoles.add(new UserRolePo(user.getId(), roleId));
                    }
                }
                userRoleMapper.insertBatch(userRoles);
            }
        }
        return Response.success();
    }

    @Override
    public List<UserVo> exportUserList(UserQo qo) {
        List<UserVo> users = baseMapper.selectUserListByCondition(qo);
        if (CollUtil.isNotEmpty(users)) {
            Map<Integer, String> departmentMap = departmentMapper.selectList(Wrappers.emptyWrapper())
                    .stream()
                    .collect(Collectors.toMap(DepartmentPo::getId, DepartmentPo::getName));
            for (UserVo user : users) {
                String[] departmentIds = user.getDepartmentAncestors().split(StrPool.COMMA);
                if (ArrayUtil.isNotEmpty(departmentIds)) {
                    for (String departmentId : departmentIds) {
                        int id = Integer.parseInt(departmentId);
                        String name = user.getDepartmentName();
                        String departmentName = StringUtils.isBlank(name) ?
                                departmentMap.get(id) : name + StrPool.COMMA + departmentMap.get(id);
                        user.setDepartmentName(departmentName);
                    }
                }
            }
        }
        return users;
    }

    private boolean checkUsernameOrEmailOrPhoneNumberUnique(Set<String> set, List<String> data, List<UserVo> users) {
        set.clear();
        if (CollUtil.isNotEmpty(data)) {
            set.addAll(data);
        }
        long sameUsernameOrEmailCount = users.stream()
                .filter(user -> !set.add(user.getUsername()) || !set.add(user.getEmail()) || !set.add(user.getPhoneNumber()))
                .count();
        return sameUsernameOrEmailCount > 0;
    }

}